/**
 * 
 */
package org.swing.utility.input.common;

import java.util.*;
import java.awt.*;

/**
 * @author Le Quynh Nhu
 * 
 */
interface WordWrapScanner {
	// returns the index of the LAST character to fit within
	// the boundaries starting at startIndex and moving to a string
	// length defined by nextLoc
	public int scan(StringBuffer buf, int startIndex, double nextLoc);
}

class CharColumnScanner implements WordWrapScanner {
	/** nextLoc is expected to be the number of columns */
	// presently does a simple linear search
	public int scan(StringBuffer buf, int start, double nextLoc) {
		int nextIndex = start + ((int) nextLoc) - 1;
		if (buf.length() <= nextIndex)
			nextIndex = buf.length() - 1;
		// determine if there's a \n first, and if so, use it
		// as the breaking point
		for (int x = start; x < nextIndex; x++)
			if (buf.charAt(x) == '\n')
				nextIndex = x - 1;
		return nextIndex;
	}
}

class FontMetricsScanner implements WordWrapScanner {
	FontMetrics metrics;

	public FontMetricsScanner(FontMetrics metrics) {
		this.metrics = metrics;
	}

	// nextLoc is expected to be the number of columns
	// presently does a simple linear search, very expensive.
	public int scan(StringBuffer buf, int start, double nextLoc) {
		// gather the array
		char[] chars = new char[buf.length() - start];
		buf.getChars(start, buf.length(), chars, 0);

		// start computing the lengths. Don't bother if we get to a \n
		for (int x = 0; x < chars.length; x++) {
			if (chars[x] == '\n')
				return start + x - 1;
			int len = metrics.charsWidth(chars, 0, x + 1);
			if (len > nextLoc) // including x was bad
				return start + x - 1;
		}
		// everything fit
		return buf.length() - 1;
	}
}

/**
 * WordWrap is a simple word-wrapping class which provides word-wrap either to
 * columns of raw text; or to some number of pixels (given a font). It's not
 * terribly efficient but works reasonably well. Tabs are considered to be the
 * same length as spaces. The input is the string to wrap, and the output is the
 * same string with returns inserted in the appropriate location.
 */

// in the future we might provide arbitrary scanners to determine maximal
// length, but right now it's just these two.

public class WordWrap implements java.io.Serializable {

	/** Wraps a string to a given number of columns. */
	public static String wrap(String string, int numColumns) {
		return wrap(string, numColumns, new CharColumnScanner());
	}

	/**
	 * Wraps a string to a given number of pixels in width, given a font whose
	 * metrics are provided as well.
	 */
	public static String wrap(String string, int numPixels, FontMetrics metrics) {
		return wrap(string, numPixels, new FontMetricsScanner(metrics));
	}

	static String wrap(String string, double desiredLength,
			WordWrapScanner scanner) {
		StringBuffer buf = new StringBuffer(string);

		int s = 0;
		int e;

		while (true) {
			if (s == buf.length()) // gone too far
				return buf.toString();

			e = scanner.scan(buf, s, desiredLength) + 1;

			if (e >= buf.length()) // up to last character
				return buf.toString();

			char ce = buf.charAt(e);

			if (ce == '\n') {
				s = e + 1;
			} else if (Character.isWhitespace(ce)) {
				int top = e;
				while (top < buf.length() - 1 && // not last character
						Character.isWhitespace(buf.charAt(top)) && // it's
																	// whitespace
						buf.charAt(top) != '\n')
					// but it's not an '\n'
					top++;
				buf.delete(e, top); // yank out all whitespace to the next value
				if (buf.charAt(e) != '\n') // not already a \n, need to add one
					buf.insert(e, '\n');
				s = e + 1;
			} else // not whitespace
			{
				int l = e;
				while (l > s && // don't back beyond s
						!Character.isWhitespace(buf.charAt(l)))
					// not a whitespace char
					l--;
				if (l == s && !Character.isWhitespace(buf.charAt(l))) // oops,
																		// all
																		// non-whitespace,
																		// must
																		// split
				{
					buf.insert(e, '\n');
					s = e + 1;
				} else {
					buf.insert(l + 1, '\n');
					s = l + 2;
				}
			}
		}
	}

	/**
	 * A useful auxillary method: once you're word-wrapped your text, you can
	 * use this to break it into multiple strings at the \n position.
	 */
	public static String[] split(String str) {
		StringTokenizer tok = new StringTokenizer(str, "\n");
		String[] s = new String[tok.countTokens()];
		int x = 0;
		while (tok.hasMoreTokens())
			s[x++] = tok.nextToken();
		return s;
	}

	/**
	 * A useful auxillary method: once you've word-wrapped your text, you can
	 * use this to convert it into 'HTML' style, where <b>&lt;</b> is converted
	 * into <b>&amp;lt;</b>, <b>&amp;</b> is converted into <b>&amp;amp;</b>,
	 * and <b>\n</b> or <b>\r</b> are converted into <b>&lt;br></b>.
	 * 
	 * <p>
	 * You can use this to make multi-line buttons and multi-line menus like
	 * this:
	 * 
	 * <pre>
	 * <tt>
	 *     String myText = "Here is the big text that we want to have word-wrapped";
	 *     int myNumberOfPixels = 50; // our word-wrap pixel length
	 *     JButton button = new JButton();
	 *     String wrappedText = sim.util.WordWrap.wrap(myText, myNumberOfPixels, button.getFontMetrics(button.getFont()));
	 *     button.setText("&lt;html>" + sim.util.WordWrap.toHTML(wrappedText) + "&lt;/html>");
	 *     </tt>
	 * </pre>
	 */

	public static String toHTML(final String text) {
		StringBuffer buf = new StringBuffer();
		char[] c = text.toCharArray();
		for (int x = 0; x < c.length; x++) {
			switch (c[x]) {
			case (char) 10:
			case (char) 13:
				buf.append("<br>");
				break;
			case '&':
				buf.append("&amp;");
				break;
			case '<':
				buf.append("&lt;");
				break;
			default:
				buf.append(c[x]);
				break;
			}
		}
		return buf.toString();
	}

}
